#!/usr/bin/python

"""
Memory structures are used for data storage and random access.
"""

from utils import *
from structure import *
from logger import logger

class MemoryError(Exception):
    """
    handling class for memory exceptions
    """
    
    def __init__(self, msg):
        """
        initialize the class
        """
        logger.error("memory: %s" % msg)

class Memory(Structure):
    """
    a structure that stores data
    """
    
    def __init__(self, dim = MEMORY_DIMENSION):
        """
        initialize a memory structure
        """
        Structure.__init__(self, dim, 1, 'M')
        
    def compute(self):
        """
        use the inputs to calculate an output

        required format:
        input[0] -> ra, the read address and read enable
        input[1] -> wa, the write address and write enable
        input[2] -> wi, the write input
        input[3] -> ri, the read input (used as a default value for ro)

        the input data is a set of binary strings, MSB -> LSB
        """
        # need all four inputs
        if len(self.inputs) != 4:
            raise MemoryError('incorrect number of inputs')
        
        #store the inputs in a more friendly format
        [ra, wa, wi, ri] = self.inputs
        
        #separate read/write enables from addresses
        we, wa = self.separate_en(wa)
        re, ra = self.separate_en(ra)

        #can't read and write at the same time
        if we == True and re == True:
            #output is the default
            self.result = ri
        elif we == True:
            #write the data to the specified address
            self.data_write(wa, wi)
            #output is the default
            self.result = ri
        elif re == True:
            #output is the data at the specified address
            self.result = self.data_read(ra)
            
    def separate_en(self, binary_str):
        """
        Separate the enabled flag from the address and return both as a tuple.
        
        @param binary_str: a binary string consisting of an enable bit followed by an address
        @return a split version of the binary string
        @rtype tuple of a bool and a string
        """
        #the enable flag is the MSB
        #the rest of the string is an address
        return bool(int(binary_str[0])), binary_str[1:]
    
    def data_read(self, addr):
        """
        read the specified address and return the contents
        
        @param addr: the address to read
        
        @return: the data found at that address
        """
        #Assumes that data length and memory word length are the same
        data = ''
        
        #calculate the dimensions of the memory
        cellfield_height = len(self.cellfield)
        cell_height = len(self.cellfield[0][0].matrix)
        element_height = len(self.cellfield[0][0].matrix[0][0].element)
        cellfield_width = len(self.cellfield[0])
        cell_width = len(self.cellfield[0][0].matrix[0])
        element_width = len(self.cellfield[0][0].matrix[0][0].element)
                
        #based on the address, find the correct memory row
        cellfield_row = addr / (cell_height * element_height)
        cell_row = (addr % (cell_height * element_height)) / element_height
        element_row = addr % element_height
        
        #write to the cells
        for cellfield_col in range(cellfield_width):
            curr_cell = self.cellfield[cellfield_row][cellfield_col]
            for cell_col in range(cell_width):
                curr_element = curr_cell.matrix[cell_row][cell_col]
                for element_col in range(element_width):
                    data += curr_element.read(element_row, element_col)
                    
        return data
    
    def data_write(self, addr, data):
        """
        write the given data to the specified address
        
        @param addr: the address to write to
        @param data: the data to write
        
        @return: nothing
        """
        #Assumes that data length and memory word length are the same
        
        #calculate the dimensions of the memory
        cellfield_height = len(self.cellfield)
        cell_height = len(self.cellfield[0][0].matrix)
        element_height = len(self.cellfield[0][0].matrix[0][0].element)
        cellfield_width = len(self.cellfield[0])
        cell_width = len(self.cellfield[0][0].matrix[0])
        element_width = len(self.cellfield[0][0].matrix[0][0].element)
                
        #based on the address, find the correct memory row
        cellfield_row = addr / (cell_height * element_height)
        cell_row = (addr % (cell_height * element_height)) / element_height
        element_row = addr % element_height
        
        #write to the cells
        for cellfield_col in range(cellfield_width):
            curr_cell = self.cellfield[cellfield_row][cellfield_col]
            for cell_col in range(cell_width):
                curr_element = curr_cell.matrix[cell_row][cell_col]
                for element_col in range(element_width):
                    curr_element.write(element_row, element_col, 
                                       data[:element_width][element_col])
                data = data[element_width:]
